#region Imported Namespaces


using System;
using System.Collections;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.IO;
using System.Reflection;


#endregion


namespace WtfManager.Framework
{
  ///<summary>
  /// A core of Wtf Framework. WtfFramework manages framework modules and services.
  ///</summary>
  public class WtfFramework
  {
    #region Fields


    private static WtfFramework _instance;
    private readonly ModulesCollection _modules = new ModulesCollection();
    private readonly ServicesCollection _services = new ServicesCollection();
    private string _modulesDirectory;


    #endregion


    #region Constructors/Finalizers


    private WtfFramework()
    {
      _modules.CollectionChanged += ModulesCollectionChanged;
    }


    #endregion


    #region Properties/Indexers


    ///<summary>
    ///Gets an instance of Wtf Framework.
    ///</summary>
    public static WtfFramework Instance
    {
      get
      {
        if (_instance == null)
        {
          _instance = new WtfFramework();
        }

        return _instance;
      }
    }


    ///<summary>
    /// Gets a <see cref="ObservableCollection{T}"/> of <see cref="Module">modules</see>
    ///</summary>
    public ReadOnlyModulesCollection Modules
    {
      get
      {
        return new ReadOnlyModulesCollection(_modules);
      }
    }


    /// <summary>
    /// Gets a modules directory.
    /// </summary>
    public string ModulesDirectory
    {
      get
      {
        return _modulesDirectory;
      }
    }


    ///<summary>
    /// Gets a <see cref="ReadOnlyServicesCollection">collection</see> of <see cref="Service">services</see>.
    ///</summary>
    public ReadOnlyServicesCollection Services
    {
      get
      {
        return new ReadOnlyServicesCollection(_services);
      }
    }


    #endregion


    #region Methods


    /// <summary>
    /// Initializes Wtf Framework.
    /// </summary>
    public void Initialize()
    {
      string currentDirectory = Directory.GetCurrentDirectory();
      string modulesDirectory = Path.Combine(currentDirectory, "Modules");

      Initialize(modulesDirectory);
    }


    /// <summary>
    /// Initializes Wtf Framework with the specified modules directory.
    /// </summary>
    /// <param name="modulesDirectory">An instance of a <see cref="System.String"/>.</param>
    public void Initialize(string modulesDirectory)
    {
      _modulesDirectory = modulesDirectory;

      try
      {
        string[] assemblies = Directory.GetFiles(modulesDirectory, "*.dll");

        foreach (string assemblyName in assemblies)
        {
          Type[] types;
          Assembly assembly;

          try
          {
            assembly = Assembly.LoadFrom(assemblyName);
            types = assembly.GetTypes();
          }
          catch (Exception e)
          {
            continue;
          }

          foreach (Type type in types)
          {
            if (type.IsSubclassOf(typeof(Module)))
            {
              try
              {
                var moduleInstance = (Module)assembly.CreateInstance(type.FullName);
                _modules.Add(moduleInstance);
              }
              catch (Exception e)
              {
                continue;
              }
            }
          }
        }
      }
      catch (Exception e)
      {
        return;
        //TODO: Log this.
      }
      return;
    }


    private void ModulesCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
      switch (e.Action)
      {
        case NotifyCollectionChangedAction.Add:
          ProcessNewModules(e.NewItems);
          break;

        case NotifyCollectionChangedAction.Remove:
          ProcessRemovedModules(e.OldItems);
          break;

        case NotifyCollectionChangedAction.Replace:
          ProcessRemovedModules(e.OldItems);
          ProcessNewModules(e.NewItems);
          break;
      }
    }


    private void ProcessRemovedModules(IList modules)
    {
      foreach (Module module in modules)
      {
        RemoveExtensions(module);

        foreach (Service service in module.Services)
        {
          _services.Remove(service);
        }

        module.Unload();
      }
    }


    private void RemoveExtensions(Module module)
    {
      foreach (Service extension in module.Services)
      {
        foreach (Service service in _services)
        {
          if (service == extension)
          {
            continue;
          }

          if (!(service is ExtensibleService))
          {
            continue;
          }

          var extensibleService = (ExtensibleService)service;

          if (extensibleService.Extensions.Contains(extension))
          {
            extensibleService.RemoveExtension(extension);
          }
        }
      }
    }


    private void ProcessNewModules(IList modules)
    {
      foreach (Module module in modules)
      {
        module.Load();

        _services.AddRange(module.Services);
      }

      ProcessExtensions();

      foreach (Module module in modules)
      {
        module.Initialize();
      }
    }


    private void ProcessExtensions()
    {
      foreach (Service extension in _services)
      {
        foreach (Service service in _services)
        {
          if (service == extension)
          {
            continue;
          }

          if (!(service is ExtensibleService))
          {
            continue;
          }

          var extensibleService = (ExtensibleService)service;

          if (extensibleService.Contains(extension))
          {
            continue;
          }

          if (!extensibleService.IsExtension(extension))
          {
            continue;
          }

          try
          {
            extensibleService.AddExtension(extension);
          }
          catch
          {
            // TODO: Log this
            continue;
          }
        }
      }
    }


    #endregion
  }
}